package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"gopkg.in/yaml.v2"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

var (
	letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	db          *sqlx.DB
	cfg         *Config
	cnText      []rune
	enText      string
	gw          = sync.WaitGroup{}
)

func main() {
	opt := 0
	fmt.Print("读取(1)/执行(2)：")
	fmt.Scan(&opt)
	if opt == 1 {
		getTableDetail(cfg.Db.Url)
		fmt.Println("读取表信息完成")
		return
	} else if opt == 2 {

	} else {
		fmt.Println("输入参数有误")
		return
	}
	rand.Seed(time.Now().UnixNano())
	var err error
	en, err := os.ReadFile("en.txt")
	assert(err)
	enText = string(en)

	cn, err := os.ReadFile("cn.txt")
	assert(err)
	cnText = []rune(string(cn))
	gw.Add(1)
	for _, item := range cfg.Exec {
		if !item.Enable {
			continue
		}
		go exec(item)
	}
	fmt.Println("处理中...")
	gw.Wait()
	fmt.Println("结束")
}

func exec(data ExecInfo) {
	ch := make(chan int, data.ChanSize)

	id := 0
	db.Get(&id, data.Query)
	for {
		ch <- 1
		id = id + data.BatchSize
		go task(ch, id, data)
	}
}

func task(ch chan int, id int, data ExecInfo) {
	defer func() {
		<-ch
	}()
	sql := bytes.NewBufferString(data.Sql)
	values := make([]interface{}, 0)
	sqlValues := setSqlValue(len(data.Values))
	for i := 0; i < data.BatchSize; i++ {
		sql.WriteString(" ")
		sql.WriteString(sqlValues)
		if i != (data.BatchSize - 1) {
			sql.WriteString(",")
		}
		id += 1
		values = append(values, setData(data.Values, id)...)
	}
	_, err := db.Exec(sql.String(), values...)
	assert(err)

}
func setSqlValue(size int) string {
	buf := bytes.Buffer{}
	buf.WriteString("(")
	for i := 0; i < size; i++ {
		buf.WriteString(" ?")
		if i != (size - 1) {
			buf.WriteString(",")
		}
	}
	buf.WriteString(")")
	return buf.String()
}
func setData(values []string, id int) []interface{} {
	vs := make([]interface{}, len(values))
	for i, v := range values {
		if strings.HasPrefix(v, "$autoid") {
			vs[i] = id
		} else if strings.HasPrefix(v, "$int_") {
			size, err := strconv.ParseInt(strings.Split(v, "_")[1], 10, 64)
			assert(err)
			vs[i] = rand.Intn(int(size))

		} else if strings.HasPrefix(v, "$str_en_") {
			size, err := strconv.ParseInt(strings.Split(v, "_")[2], 10, 64)
			assert(err)
			start := rand.Intn(len(enText) - int(size))
			vs[i] = enText[start : start+int(size)]

		} else if strings.HasPrefix(v, "$str_cn_") {
			size, err := strconv.ParseInt(strings.Split(v, "_")[2], 10, 64)
			assert(err)
			start := rand.Intn(len(cnText) - int(size))
			vs[i] = string(cnText[start : start+int(size)])

		} else if strings.HasPrefix(v, "$rand_str_") {
			size, err := strconv.ParseInt(strings.Split(v, "_")[2], 10, 64)
			assert(err)
			vs[i] = randStr(int(size))
		} else if strings.HasPrefix(v, "$datetime") {
			vs[i] = time.Now()
		}
	}
	return vs
}

func assert(err error) {
	if err != nil {
		gw.Done()
		panic(err)
	}
}

func randStr(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

func init() {
	cfg = new(Config)
	bs, err := ioutil.ReadFile("application.yaml")
	if err != nil {
		panic("读取配置出错！")
	}
	err = yaml.Unmarshal(bs, &cfg)
	if err != nil {
		panic("加载配置出错！")
	}

	db, err = sqlx.Connect("mysql", cfg.Db.Url)
	if err != nil {
		panic(err)
	}

	db.SetMaxIdleConns(cfg.Db.MaxIdie)
	db.SetMaxOpenConns(cfg.Db.MaxOpen)
	err = db.Ping()
	if err != nil {
		panic(err)
	}
}

type Config struct {
	Db struct {
		Url     string `yaml:"url"`
		MaxIdie int    `yaml:"maxIdie"`
		MaxOpen int    `yaml:"maxOpen"`
	}
	Exec []ExecInfo `yaml:"execInfo"`
}

type ExecInfo struct {
	Sql       string   `yaml:"sql"`
	Values    []string `yaml:"values"`
	BatchSize int      `yaml:"batchSize"`
	Enable    bool     `yaml:"enable"`
	ChanSize  int      `yaml:"chanSize"`
	Query     string   `yaml:"query"`
}

func getTableDetail(url string) {
	databaseNm := url[strings.LastIndex(url, "/")+1 : strings.LastIndex(url, "?")]
	sql := `SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?`
	tables := make([]string, 0)
	err := db.Select(&tables, sql, databaseNm)
	if err != nil {
		return
	}

	f, err := os.Create("tables.txt")
	assert(err)
	for _, v := range tables {
		sql := `select column_name as c_name,data_type as d_type,column_type as  c_type  from information_schema.columns 
					where table_name=? and table_schema=?`

		result := make([]TableColumns, 0)
		err := db.Select(&result, sql, v, databaseNm)
		if err != nil {
			panic(err)
		}
		sqlBuf := bytes.Buffer{}
		sqlBuf.WriteString(`insert into ` + v + `( `)
		values := make([]string, 0)
		for i, info := range result {
			sqlBuf.WriteString(info.ColumnName)
			if i != (len(result) - 1) {
				sqlBuf.WriteString(", ")
			}
			values = append(values, setValueType(info))
		}

		sqlBuf.WriteString(" ) values ")
		f.WriteString(`  - sql: "`)
		f.WriteString(sqlBuf.String())
		f.WriteString(`"`)
		f.WriteString("\n")

		f.WriteString("    values: [")
		for i, v := range values {
			f.WriteString(`"` + v + `"`)
			if i != (len(result) - 1) {
				f.WriteString(", ")
			}
		}
		f.WriteString("]")
		f.WriteString("\n")
		f.WriteString(`    query: "select max(id) from ` + v)
		f.WriteString(`"`)
		f.WriteString("\n\n")
	}
	f.Close()
}

func setValueType(info TableColumns) string {
	if info.ColumnName == "id" {
		return "$autoid"
	}

	if strings.HasPrefix(info.DataType, "bigint") ||
		strings.HasPrefix(info.DataType, "tinyint") ||
		strings.HasPrefix(info.DataType, "smallint") ||
		strings.HasPrefix(info.DataType, "mediumint") ||
		strings.HasPrefix(info.DataType, "int") ||
		strings.HasPrefix(info.DataType, "float") ||
		strings.HasPrefix(info.DataType, "double") ||
		strings.HasPrefix(info.DataType, "decimal") ||
		strings.HasPrefix(info.DataType, "integer") ||
		strings.HasPrefix(info.DataType, "char") {
		size := getInt(info.ColumnType)
		return "$int_" + strconv.Itoa(int(size))

	} else if strings.HasPrefix(info.DataType, "varchar") {
		size := getInt(info.ColumnType)
		return "$str_en_" + strconv.Itoa(int(size))
	} else if strings.HasPrefix(info.DataType, "datetime") ||
		strings.HasPrefix(info.DataType, "timestamp") {
		return "$datetime"
	} else {
		size := getInt(info.ColumnType)
		if size != 0 {
			return "$rand_str_32"
		}
		return "$rand_str_" + strconv.Itoa(int(size))
	}
}

type TableColumns struct {
	ColumnName string `db:"c_name"`
	DataType   string `db:"d_type"`
	ColumnType string `db:"c_type"`
}

func getInt(str string) int64 {
	reg := regexp.MustCompile(`\d+`)
	v := reg.FindString(str)
	if v == "" {
		return 0
	}
	vv, err := strconv.ParseInt(v, 10, 64)
	if err != nil {
		return 0
	}
	return vv
}
